diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-03-02 00:55:08 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-03-02 00:55:08 +0100 |
| commit | 77a3708213606d520833944141c3aa9169954e07 (patch) | |
| tree | a9d9edf5abc209a1bb02a109ceed6fc5e5c445fb /apps/web/src/app/changelog/[version] | |
| parent | 9a00e8ac5b77e8d82102457aaffd19945a2f52cb (diff) | |
| download | opencut-77a3708213606d520833944141c3aa9169954e07.tar.gz | |
web: refactor changelog to support a dedicated page for each version + links for each release
Diffstat (limited to 'apps/web/src/app/changelog/[version]')
| -rw-r--r-- | apps/web/src/app/changelog/[version]/page.tsx | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/apps/web/src/app/changelog/[version]/page.tsx b/apps/web/src/app/changelog/[version]/page.tsx new file mode 100644 index 00000000..5b24af1e --- /dev/null +++ b/apps/web/src/app/changelog/[version]/page.tsx @@ -0,0 +1,98 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { BasePage } from "@/app/base-page"; +import { allChangelogs } from "content-collections"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; +import { getSortedReleases } from "../utils"; +import { + ReleaseArticle, + ReleaseMeta, + ReleaseTitle, + ReleaseDescription, + ReleaseChanges, +} from "../components/release"; + +type Props = { params: Promise<{ version: string }> }; + +export async function generateStaticParams() { + return allChangelogs.map((r) => ({ version: r.version })); +} + +export async function generateMetadata({ params }: Props): Promise<Metadata> { + const { version } = await params; + const release = allChangelogs.find((r) => r.version === version); + if (!release) return {}; + return { + title: `${release.title} (${release.version}) - OpenCut Changelog`, + description: release.description, + }; +} + +export default async function ReleaseDetailPage({ params }: Props) { + const { version } = await params; + const releases = getSortedReleases(); + const index = releases.findIndex((r) => r.version === version); + + if (index === -1) notFound(); + + const release = releases[index]; + const newer = index > 0 ? releases[index - 1] : null; + const older = index < releases.length - 1 ? releases[index + 1] : null; + + return ( + <BasePage> + <div className="mx-auto w-full max-w-3xl flex flex-col gap-12"> + <Link + href="/changelog" + className="text-sm text-muted-foreground hover:text-foreground flex items-center gap-1 w-fit" + > + <ChevronLeftIcon className="size-4" /> + All releases + </Link> + + <ReleaseArticle variant="detail"> + <div className="flex flex-col gap-4"> + <ReleaseMeta release={release} /> + <ReleaseTitle as="h1">{release.title}</ReleaseTitle> + {release.description && ( + <ReleaseDescription>{release.description}</ReleaseDescription> + )} + </div> + <ReleaseChanges release={release} /> + </ReleaseArticle> + + <nav className="flex items-center justify-between border-t pt-8"> + {older ? ( + <Link + href={`/changelog/${older.version}`} + className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground group" + > + <ChevronLeftIcon className="size-4" /> + <div className="flex flex-col"> + <span className="text-xs text-muted-foreground/60">Older</span> + <span className="font-medium">{older.title}</span> + </div> + </Link> + ) : ( + <div /> + )} + {newer ? ( + <Link + href={`/changelog/${newer.version}`} + className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground group text-right" + > + <div className="flex flex-col"> + <span className="text-xs text-muted-foreground/60">Newer</span> + <span className="font-medium">{newer.title}</span> + </div> + <ChevronRightIcon className="size-4" /> + </Link> + ) : ( + <div /> + )} + </nav> + </div> + </BasePage> + ); +} |
